/* *****************************************************************************************************************************
 * (c) J@mBeL.net 2010-2015
 * Author: John Ambeliotis
 * Created: 24 Apr. 2010
 *
 * License:
 *  This file is part of Project jaNET.

    Project jaNET is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Project jaNET is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Project jaNET. If not, see <http://www.gnu.org/licenses/>.
   
    Resources: http://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener.aspx
               http://www.codehosting.net/blog/BlogEngine/post/Simple-C-Web-Server.aspx */

using System;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.IO;
using System.Collections.Generic;
using System.Linq;

namespace libJanet
{
    class Server
    {
        internal class TCP
        {
            private static TcpListener server = null;
            internal static volatile bool ServerState;

            internal static void Start()
            {
                if (!ServerState)
                {
                    Thread t = new Thread(() => {
                        try {
                            ListenForClients();
                        }
                        catch { }
                    });
                    t.IsBackground = true;
                    t.Start();
                }
            }

            internal static void Stop()
            {
                if (ServerState)
                {
                    server.Stop();
                    ServerState = false;
                }
            }

            private static void ListenForClients()
            {
                try
                {
                    IPAddress localAddr = IPAddress.Parse(
                        Queries.Xml.AppConfigQuery(
                        ApplicationSettings.ApplicationStructure.LocalHostPath)
                        .Item(0).InnerText.Replace("localhost", "127.0.0.1"));
                    Int32 port = Convert.ToInt32(
                        Queries.Xml.AppConfigQuery(
                        ApplicationSettings.ApplicationStructure.LocalPortPath)
                        .Item(0).InnerText);

                    server = new TcpListener(localAddr, port);
                    // Start listening for client requests.
                    server.Start();

                    ServerState = true;

                    // Buffer for reading data
                    Byte[] bytes = new Byte[1024];
                    String data = null;

                    // Enter the listening loop.
                    while (ServerState)
                    {
                        // Perform a blocking call to accept requests.
                        // You could also user server.AcceptSocket() here.
                        TcpClient client = server.AcceptTcpClient();
                        // Get a stream object for reading and writing
                        NetworkStream stream = client.GetStream();
                        data = null;
                        int i;

                        // Loop to receive all the data sent by the client.
                        while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
                        {
                            byte[] response = null;
                            // Translate data bytes to a ASCII string.
                            data += Web.SimpleUriDecode(Encoding.ASCII.GetString(bytes, 0, i));

                            Match mItem = Regex.Match(data, "GET.*HTTP");

                            if (mItem.Success)
                            {
                                data = mItem.ToString().Replace("GET /", string.Empty).Replace("HTTP", string.Empty).Trim() + "\r\n";
                                if (data.ToLower().Contains("favicon.ico"))
                                    break;
                            }

                            try
                            {
                                if (!Queries.Xml.AppConfigQuery("jaNET/System/Comm/Trusted").Item(0).InnerText.Replace("localhost", "127.0.0.1").Contains(
                                      IPAddress.Parse(((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString()).ToString()
                                  ))
                                {
                                    throw new UnauthorizedAccessException();
                                }
                                else if (data.IndexOf("\r\n") >= 0)
                                {
                                    response = Encoding.ASCII.GetBytes(Parser.Parse(data.Replace("\r\n", string.Empty)) + "\r\n");
                                    // Send back a response.
                                    stream.Write(response, 0, response.Length);
                                    data = string.Empty;
                                    if (mItem.Success)
                                        break; // HTTP post, need to break when finished
                                }
                            }
                            catch (UnauthorizedAccessException e)
                            {
                                response = Encoding.ASCII.GetBytes(e.Message + "\r\n");
                                stream.Write(response, 0, response.Length);
                                data = string.Empty;
                            }

                            if (!ServerState)
                                throw new SocketException();
                        }
                        // Shutdown and end connection
                        client.Close();
                    }
                }
                catch (SocketException e)
                {
                    server.Stop();
                    ServerState = false;
                    Debug.Print("SocketException: {0}", e);
                }
                /*finally
                {
                // Stop listening for new clients.
                ServerState = false;
                server.Stop();
                }*/
            }
        }

        internal class Web
        {
            private class Login
            {		
                private string Username { get; set; }
                private string Password { get; set; }

                internal Login()
                {
                    IList<String> webLogin = new FileSystem.Settings().LoadSettings(".htaccess");

                    if (webLogin != null)
                    {
                        Username = webLogin[0];
                        Password = webLogin[1];
                    }
                }

                internal bool Authenticate(string u, string p)
                {
                    if (this.Username == u && this.Password == p)
                        return true;
                    else
                        return false;
                }
            }

            internal static readonly HttpListener httplistener = new HttpListener();

            internal static void Start()
            {
                string[] MIME_Image = { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico" };
                string[] MIME_Text = { ".html", ".htm", ".xml", ".css", ".js", ".txt" };
                string Prefix = string.Empty;
                string AuthenticationType = string.Empty;

                try {
                    Prefix = "http://" + Queries.Xml.AppConfigQuery(
                                            ApplicationSettings.ApplicationStructure.HttpHostNamePath)
                                            .Item(0).InnerText + ":" +
                                         Queries.Xml.AppConfigQuery(
                                            ApplicationSettings.ApplicationStructure.HttpPortPath)
                                            .Item(0).InnerText + "/";

                    AuthenticationType = Queries.Xml.AppConfigQuery(
                                            ApplicationSettings.ApplicationStructure.HttpAuthenticationPath)
                                            .Item(0).InnerText;

                    if (!HttpListener.IsSupported)
                        throw new NotSupportedException(
                            "Server is not supported.");

                    if (httplistener.IsListening)
                        return;
                    
                    httplistener.Prefixes.Clear();
                    httplistener.Prefixes.Add(Prefix);
                    if (AuthenticationType.ToLower() == "basic")
                        httplistener.AuthenticationSchemes = AuthenticationSchemes.Basic;
                    else
                        httplistener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
                    httplistener.IgnoreWriteExceptions = true;
                    httplistener.Start();
                }
                catch (NullReferenceException)
                {
                    FileSystem.Log.Append("obj [ Server.Web.Start <Exception> ]: PreThread [ NullReferenceException ]");
                }
                catch (Exception e) {
                    FileSystem.Log.Append("obj [ Server.Web.Start <Exception> ]: PreThread [ " + e.Message + " ]");
                    return;
                }
        		
                ThreadPool.QueueUserWorkItem((o) =>
                {
                    try
                    {
                        while (httplistener.IsListening)
                        {
                            ThreadPool.QueueUserWorkItem((c) =>
                            {
                                var ctx = c as HttpListenerContext;

                                try
                                {
                                    byte[] buf = null;

                                    if (isAuthenticated(ctx))
                                    {
                                        string mapPath = Methods.getApplicationPath() + SimpleUriDecode(ctx.Request.RawUrl.Substring(1));

                                        if (mapPath.EndsWith("/"))
                                            mapPath += "index.html";

                                        if (Array.Find(MIME_Image, s => s.Contains(mapPath.Substring(mapPath.LastIndexOf('.')))) != null)
                                            buf = File.ReadAllBytes(mapPath);
                                        else if (Array.Find(MIME_Text, s => s.Contains(mapPath.Substring(mapPath.LastIndexOf('.')))) != null || mapPath.Contains("?cmd="))
                                            buf = Encoding.UTF8.GetBytes(SendResponse(mapPath));
                                    }
                                    else
                                    {
                                        ctx.Response.StatusCode = 401;
                                        if (OperatingSystem.Version == OperatingSystem.Type.Unix)
                                            ctx.Response.AddHeader("WWW-Authenticate", "Basic Realm=\"Authentication Required\""); // show login dialog
                                        buf = Encoding.UTF8.GetBytes("<html><head><title>401 Authorization Required</title></head>" +
                                        "<body><h1>Authorization Required</h1>This server could not verify that you are authorized to access the document requested. Either you supplied the wrong credentials (e.g., bad password), or your browser doesn't understand how to supply the credentials required.<hr>" +
                                        "</body></html>");
                                    }

                                    ctx.Response.ContentLength64 = buf.Length;
                                    ctx.Response.OutputStream.Write(buf, 0, buf.Length);
                                }
                                catch (Exception e) {
                                    FileSystem.Log.Append("obj [ Server.Web.Start <Exception> ]: ThreadMain [ " + e.Message + " ]");
                                }
                                finally
                                {
                                    try {
                                        // always close the stream
                                        ctx.Response.OutputStream.Close();
                                    }
                                    catch (Exception e) {
                                        FileSystem.Log.Append("obj [ Server.Web.Start <Exception> ]: Thread Finally Block [ " + e.Message + " ]: action [ Restart ]");
                                        Restart();
                                    }
                                }
                            }, httplistener.GetContext());
                        }
                    }
                    catch (Exception e) {
                        FileSystem.Log.Append("obj [ Server.Web.Start <Exception> ]: Thread Generic [ " + e.Message + " ]");
                        Restart();
                    }
                });
            }

            private static bool isAuthenticated(HttpListenerContext ctx)
            {
                if (httplistener.AuthenticationSchemes == AuthenticationSchemes.Basic)
                {
                    HttpListenerBasicIdentity identity = (HttpListenerBasicIdentity)ctx.User.Identity;
                    return new Login().Authenticate(identity.Name, identity.Password);
                }
                return true;
            }

            internal static void Stop()
            {
                httplistener.Stop();
            }

            internal static void Restart()
            {
                httplistener.Stop();
                Thread.Sleep(1000);
                Start();
            }

            private static string SendResponse(string request) //(HttpListenerRequest request)
            {
                WebClient client = new WebClient();

                if (!request.Contains("?cmd="))
                    return client.DownloadString(request);
                else if (request.Contains("?cmd="))
                    return Parser.Parse(request.Substring(request.LastIndexOf("?cmd="))
                                                                 .Replace("?cmd=", string.Empty), true, false);

                return string.Empty;
            }

            internal static string SimpleUriDecode(string Uri)
            {
                return Uri.Replace("%20", " ")
                          .Replace("%21", "!")
                          .Replace("%22", "\"")
                          .Replace("%23", "#")
                          .Replace("%24", "$")
                          .Replace("%25", "%")
                          .Replace("%26", "&")
                          .Replace("%27", "'")
                          .Replace("%28", "(")
                          .Replace("%29", ")")
                          .Replace("%2A", "*")
                          .Replace("%2B", "+")
                          .Replace("%2C", ",")
                          .Replace("%2D", "-")
                          .Replace("%2E", ".")
                          .Replace("%2F", "/")
                          .Replace("%3A", ":")
                          .Replace("%3B", ";")
                          .Replace("%3C", "<")
                          .Replace("%3D", "=")
                          .Replace("%3E", ">")
                          .Replace("%3F", "?")
                          .Replace("%40", "@")
                          .Replace("%5B", "[")
                          .Replace("%5C", @"\")
                          .Replace("%5D", "]")
                          .Replace("%5E", "^")
                          .Replace("%5F", "_")
                          .Replace("%60", "`")
                          .Replace("%7B", "{")
                          .Replace("%7C", "|")
                          .Replace("%7D", "}")
                          .Replace("%7E", "~");
            }

            internal static string SimpleUriEncode(string Uri)
            {
                return Uri.Replace(" ", "%20")
                          .Replace("!", "%21")
                          .Replace("\"", "%22")
                          .Replace("#", "%23")
                          .Replace("$", "%24")
                          .Replace("%", "%25")
                          .Replace("&", "%26")
                          .Replace("'", "%27")
                          .Replace("(", "%28")
                          .Replace(")", "%29")
                          .Replace("*", "%2A")
                          .Replace("+", "%2B")
                          .Replace(",", "%2C")
                          .Replace("-", "%2D")
                          .Replace(".", "%2E")
                          .Replace("/", "%2F")
                          .Replace(":", "%3A")
                          .Replace(";", "%3B")
                          .Replace("<", "%3C")
                          .Replace("=", "%3D")
                          .Replace(">", "%3E")
                          .Replace("?", "%3F")
                          .Replace("@", "%40")
                          .Replace("[", "%5B")
                          .Replace(@"\", "%5C")
                          .Replace("]", "%5D")
                          .Replace("^", "%5E")
                          .Replace("_", "%5F")
                          .Replace("`", "%60")
                          .Replace("{", "%7B")
                          .Replace("|", "%7C")
                          .Replace("}", "%7D")
                          .Replace("~", "%7E");
            }
        }
    }
}